package checker

import (
	"kumachan/interpreter/lang/textual/ast"
	"kumachan/interpreter/lang/common/name"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/checker/typsys"
)


type resolvedMethod interface { resolvedMethod() }
func (concreteTypeMethods) resolvedMethod() {}
type concreteTypeMethods struct {
	FuncRefs  FuncRefs
}
func (interfaceTypeMethod) resolvedMethod() {}
type interfaceTypeMethod struct {
	Index  uint
	Type   typsys.Type
}
func (cc *checkContext) resolveMethodWithSubtyping(t typsys.Type, m string) (resolvedMethod, bool) {
	var def, args, ok = typeGetRefDefArgs(t)
	if ok {
		var method, ok = cc.resolveMethod(def, args, m)
		if ok {
			return method, true
		} else {
			var ctx_mod = cc.exprContext.ModName
			var super, _, ok = typsys.Unbox(t, ctx_mod)
			if ok {
				return cc.resolveMethodWithSubtyping(super, m)
			} else {
				return nil, false
			}
		}
	} else {
		return nil, false
	}
}
func (cc *checkContext) resolveMethod(def *typsys.TypeDef, type_args ([] typsys.Type), m string) (resolvedMethod, bool) {
	var I, is_interface = def.Content.(*typsys.Interface)
	if is_interface {
		var index, exists = I.Methods.FieldIndexMap[m]
		var raw_type = I.Methods.Fields[index].Type
		var params = def.Parameters
		var type_ = typsys.TypeOpApplyTypeArgs(raw_type, params, type_args)
		if !(exists) { return nil, false }
		return interfaceTypeMethod {
			Index: index,
			Type:  type_,
		}, true
	}
	var mod = def.Name.ModuleName
	var refs, ok = cc.exprContext.lookupFuncRefs(name.Name { ItemName: m }, mod)
	if !(ok) { return nil, false }
	var methods = make([] *Function, 0)
	for _, f := range refs.Functions {
		if f.AstNode.Kind == ast.FK_Method {
			var f_in_def, ok = typeGetRefDef(f.Signature.InputOutput.Input)
			if ok && f_in_def == def {
				methods = append(methods, f)
			}
		}
	}
	if len(methods) == 0 { return nil, false }
	return concreteTypeMethods { FuncRefs { methods } }, true
}

func (cc *checkContext) resolveName(n name.Name, pivot typsys.Type) (Ref, bool) {
	var lm = cc.exprContext.LocalBindingMap
	if pivot == nil {
		var ctx_mod = cc.exprContext.ModName
		return cc.exprContext.lookupRef(n, ctx_mod, lm)
	} else {
		var pivot_def, exists = typeGetRefDef(pivot)
		if exists && n.ModuleName == "" {
			var pivot_mod = pivot_def.Name.ModuleName
			return cc.exprContext.lookupRef(n, pivot_mod, lm)
		} else {
			return cc.resolveName(n, nil)
		}
	}
}
func (cc *checkContext) resolveInlineRef(node ast.InlineRef, pivot typsys.Type) (Ref, ([] typsys.Type), *source.Error) {
	var n, err = NameFrom(node.Module, node.Item, cc.exprContext.ModuleInfo)
	if err != nil { return nil, nil, err }
	var ref, found = cc.resolveName(n, pivot)
	if found {
		var params = make([] typsys.Type, 0)
		var cons_ctx = cc.typeConsContext()
		for _, param_node := range node.TypeArgs {
			var t, err = newType(param_node, cons_ctx)
			if err != nil { return nil, nil, err }
			params = append(params, t)
		}
		return ref, params, nil
	} else {
		var pivot_desc = (func() string {
			if pivot != nil {
				return typsys.DescribeType(pivot, nil)
			} else {
				return ""
			}
		})()
		return nil, nil, source.MakeError(node.Location,
			E_NoSuchBindingOrFunction {
				Name:  n.String(),
				Pivot: pivot_desc,
			})
	}
}


